package top.jnaw.jee.utils.wechat;

import com.alibaba.fastjson.JSONObject;
import com.jfinal.core.Controller;
import com.jfinal.kit.PropKit;
import com.jfinal.kit.StrKit;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.HashMap;
import top.jnaw.jee.platform.Consts;
import top.jnaw.jee.platform.Consts.Result;
import top.jnaw.jee.utils.Cache;
import top.jnaw.jee.utils.Https;
import top.jnaw.jee.utils.Log;
import top.jnaw.jee.utils.ModelResult;
import top.jnaw.jee.utils.Strings;

public class Wechats {

  /**
   * appid, secret, => access_token
   */
  private static final String FORMATTER_TOKEN = PropKit
      .get("wechat_token_formatter");

  /**
   * access_token => ticket
   */
  private static final String FORMATTER_TICKET = PropKit
      .get("wechat_ticket_formatter");

  /**
   * appid, redirect_url, state => code, state
   */
  private static final String FORMATTER_AUTH_BASE = PropKit
      .get("wechat_auth_base_formatter");

  /**
   * appid, redirect_url, state => code, state
   */
  private static final String FORMATTER_AUTH_USERINFO = PropKit
      .get("wechat_auth_userinfo_formatter");

  /**
   * appid, secret, code => access_token, refresh_token, openid, scope
   */
  private static final String FORMATTER_AUTH_TOKEN = PropKit
      .get("wechat_auth_token_formatter");

  /**
   * appid, refresh_token => access_token, refresh_token, openid, scope
   */
  private static final String FORMATTER_AUTH_REFRESH_TOKEN_USERINFO = PropKit
      .get("wechat_auth_refresh_token_formatter");

  /**
   * access_token, [next_openid] => total, count, data: {openid, next_openid}
   */
  private static final String FORMATTER_GET_USER_LIST = PropKit
      .get("wechat_auth_get_user_list_formatter");

  /**
   * access_token, openid => nickname, sex, headimageurl, unionid
   */
  private static final String FORMATTER_GET_USER_INFO = PropKit
      .get("wechat_auth_get_user_info");

  /**
   * access_token, openid => openid, nickname, headimgurl, unionid
   */
  private static final String FORMATTER_AUTH_GET_USERINFO = PropKit
      .get("wechat_auth_get_userinfo_formatter");

  private static final String ADD_MEDIA = PropKit
      .get("wechat_auth_add_media");

  private static final String FORMATTER_SIGN_TICKET_CONTENT = "jsapi_ticket=%s&noncestr=%s&timestamp=%d&url=%s";
  private static final String MEDIA_FORMATTER = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s";
  private static final String MATERIAL_FORMATTER = "https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=%s";
  private static HashMap<String, String> REDIRECTS = new HashMap<>();

  private static RequestResult requestForKey(String url, String key) {
    RequestResult result = null;

    String res = Https.get(url);
    if (StrKit.notBlank(res)) {
      try {
        JSONObject json = JSONObject.parseObject(res);
        result = new RequestResult(json, key);
      } catch (Exception e) {
        // [Neo] .Empty
      }
    }

    return result;
  }

  public static String getAccessToken() {
    String token = (String) Cache.get(Consts.ACCESS_TOKEN);

    Log.w("at from cache: " + token);

    if (StrKit.isBlank(token)) {
      String url = String
          .format(FORMATTER_TOKEN, Consts.WECHAT_APPID, Consts.WECHAT_SECRET);
      RequestResult result = requestForKey(url, Consts.ACCESS_TOKEN);

      token = null != result ? result.result : null;
      Log.e("token:::@@" + token);
      if (StrKit.notBlank(token)) {
        Cache.setx(Consts.ACCESS_TOKEN, token, 7200);
      }
    } else {
      String u = String.format(FORMATTER_GET_USER_LIST, token, "");

      RequestResult list = requestForKey(u, "total");
      if (!list.fine) {
        Log.e("request at again");

        String url = String
            .format(FORMATTER_TOKEN, Consts.WECHAT_APPID, Consts.WECHAT_SECRET);
        RequestResult result = requestForKey(url, Consts.ACCESS_TOKEN);

        token = null != result ? result.result : null;
        Log.e("token:::@@2：" + token);
        if (StrKit.notBlank(token)) {
          Cache.setx(Consts.ACCESS_TOKEN, token, 7200);
        }
      }
    }
    return token;
  }

  public static File getMedia(String id, String file) {
    File result = null;
    String token = Wechats.getAccessToken();

    if (StrKit.notBlank(token, id)) {
      String url = String.format(MEDIA_FORMATTER, token, id);
      result = Https.getFile(url, file);
    }

    return result;
  }

  public static ModelResult getMaterial(String type, Integer offset,
      Integer count) {
    ModelResult result = new ModelResult(Result.kBadParam);
    String token = Wechats.getAccessToken();
    if (StrKit.notBlank(token)) {
      String url = String.format(MATERIAL_FORMATTER, token);
      JSONObject object = new JSONObject();
      object.put("type", type);
      object.put("offset", offset);
      object.put("count", count);
      String post = Https.post(url, object.toJSONString());
      result.setObject(JSONObject.parseObject(post));
      result.setResult(Result.kSuccess);
    }
    return result;
  }

//  public static File addMedia(JSONArray articles) {
//    String token = Wechats.getAccessToken();
//
//    if (StrKit.notBlank(token)) {
//      String url = String.format(MEDIA_FORMATTER, token, id);
//      result = Https.getFile(url, file);
//    }
//
//    return result;
//  }

  public static String getTicket() {
    String ticket = (String) Cache.get(Consts.TICKET);

    if (StrKit.isBlank(ticket)) {
      String token = getAccessToken();

      if (StrKit.notBlank(token)) {
        String url = String.format(FORMATTER_TICKET, token);
        RequestResult result = requestForKey(url, Consts.TICKET);

        ticket = null != result ? result.result : null;
        if (StrKit.notBlank(ticket)) {
          Cache.setx(Consts.TICKET, ticket, 7200);
        }
      }
    }

    return ticket;
  }

  public static JSONObject genSignedTicket(String url) {
    JSONObject result = new JSONObject();

    int ts = (int) (new Date().getTime() / 1000);
    String ticket = getTicket();
    String noncestr = Consts.WECHAT_SECRET.substring(5, 15);

    result.put("url", url);
    result.put("ticket", ticket);
    result.put("timestamp", ts);
    result.put("appid", Consts.WECHAT_APPID);
    result.put("noncestr", noncestr);

    String content = String
        .format(FORMATTER_SIGN_TICKET_CONTENT, ticket, noncestr, ts, url);
    result.put("signature", Strings.SHA1(content));

    return result;
  }


  public static void redirect(Controller controller, String state, String key) {
    redirect(controller, state, key, false);
  }

  public static void redirect(Controller controller, String state, String key,
      boolean base) {
    state = base ? state : "0" + state + "0";
    REDIRECTS.put(state, key);
    try {
      final String redirectUrl = URLEncoder
          .encode(Consts.WECHAT_REDIRECT_URL, Consts.ENCODE);

      final String url = String.format(
          base ? FORMATTER_AUTH_BASE : FORMATTER_AUTH_USERINFO,
          Consts.WECHAT_APPID, redirectUrl, state);
      Log.i("url214:" + url);
      controller.redirect(url);
    } catch (UnsupportedEncodingException e) {
      controller.renderError(Https.CODE_NOT_ACCEPTABLE);
    }
  }

  public static JSONObject getAuthInfo(String code, String state) {
    String url = String.format(FORMATTER_AUTH_TOKEN, Consts.WECHAT_APPID,
        Consts.WECHAT_SECRET, code);
    Log.i("url:" + url);
    RequestResult token = requestForKey(url, Consts.ACCESS_TOKEN);
    Log.i("token:" + token);
    if (token.fine) {
      String openId = token.json.getString(Consts.OPENID);

      if (StrKit.notBlank(state) && state.length() >= 2 &&
          state.startsWith("0") && state.endsWith("0")) {
        url = String.format(FORMATTER_AUTH_GET_USERINFO, token.result, openId);
        RequestResult info = requestForKey(url, Consts.OPENID);
        Log.i("info:" + info);
        if (info.fine) {
          token = info;
        }
      }

      token.json.put("url", REDIRECTS.get(state));
      token.json.put("state", state.replace("0", ""));
      Log.i("token.json:" + token.json);
    }

    return token.json;
  }

  public static RequestResult refreshAuthToken(String token, String openid) {
    final String url = String
        .format(FORMATTER_AUTH_REFRESH_TOKEN_USERINFO, token, openid);
    return requestForKey(url, Consts.WECHAT_MSG);
  }

  public static ModelResult getUserList() {
    ModelResult result = new ModelResult(Result.kModelError);
    // [Neo] .TODO next openid
    String token = getAccessToken();
    String url = String.format(FORMATTER_GET_USER_LIST, token, "");

    RequestResult list = requestForKey(url, "total");

    if (list.fine) {
      for (int i = 0; i < Integer.parseInt(list.result); i++) {
        getUserInfo(
            list.json.getJSONObject("data").getJSONArray("openid").get(i)
                .toString());
      }
      // [Neo] .TODO for save
      Log.i("todo: " + list.json);
      result.setResult(Result.kSuccess);
    }

    return result;
  }

  public static ModelResult getUserInfo(String openid) {
    ModelResult result = new ModelResult(Result.kBadParam);
    final String url = String
        .format(FORMATTER_GET_USER_INFO, getAccessToken(), openid);
    RequestResult info = requestForKey(url, "nickname");
    Log.e("@@@" + info.toString());
    if (info.fine) {
//      result = WechatUsers.save(info.json);
    }
    Log.e("$$$" + result.toJson().toJSONString());
    return result;
  }

  public static ModelResult setIndustry(String openid) {
    ModelResult result = new ModelResult(Result.kBadParam);
    final String url = String
        .format(FORMATTER_GET_USER_INFO, getAccessToken(), openid);
    RequestResult info = requestForKey(url, "nickname");

    if (info.fine) {
//      result = WechatUsers.save(info.json);
    }

    return result;
  }

  private static class RequestResult {

    boolean fine;
    int code;

    String result;
    JSONObject json;

    private RequestResult(JSONObject json, String key) {
      fine = false;
      code = -1;

      if (null != json && StrKit.notBlank(key)) {
        if (json.containsKey(key) &&
            (!json.containsKey(Consts.WECHAT_CODE) ||
                json.getIntValue(Consts.WECHAT_CODE) == 0)) {
          fine = true;
          code = 0;
          result = json.getString(key);
          this.json = json;
        } else {
          fine = false;
          code = json.getIntValue(Consts.WECHAT_CODE);
          result = json.getString(Consts.WECHAT_MSG);
        }
      }
    }

    @Override
    public String toString() {
      return code + ", " + json;
    }
  }

}
